package com.googlecode.curvedmetromaps.core.model.importer;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.googlecode.curvedmetromaps.core.model.MetroLine;
import com.googlecode.curvedmetromaps.core.model.MetroMap;
import com.googlecode.curvedmetromaps.core.model.MetroStation;

public class GraphmlMapParser extends MapParser {

	// used for reading the XML-input
	static final String	DATA		= "data";
	static final String	DATA_KEY	= "key";
	static final String	DATA_X		= "x";
	static final String	DATA_Y		= "y";
	static final String	DATA_NAME	= "label";
	static final String	EDGE		= "edge";
	static final String	NODE		= "node";
	static final String	GRAPHML		= "graphml";
	static final String	GRAPH		= "graph";
	static final String	ID			= "id";
	static final String	EDGE_SOURCE	= "source";
	static final String	EDGE_TARGET	= "target";
	static final String	DUMMY		= "dummy";

	@Override
	public void parse(String resource, final MetroMap map) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		try {
			builder = factory.newDocumentBuilder();
			// reader = new StringReader(resource);
			InputSource input = new InputSource(new StringReader(resource));
			// Parse text to XML
			Document doc = builder.parse(input);
			// Parse OSM XML to MetroMap
			parseGraphml(doc, map);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static void parseGraphml(Document doc, final MetroMap metroMap) {
		// Parse metrostations into a hashmap.
		HashMap<String, MetroStation> metroStations = new HashMap<String, MetroStation>();
		NodeList stationNodes = doc.getElementsByTagName(NODE);
		for (int i = 0; i < stationNodes.getLength(); i++) {
			Node stationNode = stationNodes.item(i);
			if (stationNode.getNodeType() != Node.ELEMENT_NODE)
				continue;

			MetroStation m = new MetroStation();
			m.ref = stationNode.getAttributes().getNamedItem(ID).getNodeValue();
			// Iterate through children (x, y, label)
			Node child = stationNode.getFirstChild();
			while (child != null) {
				if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName() == DATA) {
					// child is something like <data key="x">101</data>
					String key = child.getAttributes().getNamedItem(DATA_KEY).getNodeValue();
					String value = child.getTextContent();
					if (key == null) {
						System.out.println("Key is null");
					}
					if (key.equals(DATA_X)) {
						m.geoX = Float.valueOf(value);
						m.x = m.geoX;
					} else if (key.equals(DATA_Y)) {
						m.geoY = Float.valueOf(value);
						m.y = m.geoY;
					} else if (key.equals(DATA_NAME)) {
						m.label = value;
					}
				}

				child = child.getNextSibling();
			}

			// Add MetroStation to hashmap
			metroStations.put(m.ref, m);
		}

		// Parse edges. In the following HashMap, the first key is a line id and
		// the second key a source station. The final value is a list of
		// targets.
		HashMap<String, HashMap<String, HashSet<String>>> targets = new HashMap<String, HashMap<String, HashSet<String>>>();
		NodeList edges = doc.getElementsByTagName(EDGE);
		for (int i = 0; i < edges.getLength(); i++) {
			Node edgeNode = edges.item(i);
			if (edgeNode.getNodeType() != Node.ELEMENT_NODE)
				continue;

			String source = edgeNode.getAttributes().getNamedItem(EDGE_SOURCE).getNodeValue();
			String target = edgeNode.getAttributes().getNamedItem(EDGE_TARGET).getNodeValue();

			// Iterate over the metro lines that contain this edge.
			Node child = edgeNode.getFirstChild();
			while (child != null) {
				if (child.getNodeType() == Node.ELEMENT_NODE) {
					String lineId = child.getAttributes().getNamedItem(DATA_KEY).getNodeValue();
					// Create or get hashmap that maps a source to target
					// stations.
					HashMap<String, HashSet<String>> lineTargets;
					if (targets.containsKey(lineId) == false) {
						lineTargets = new HashMap<String, HashSet<String>>();
						targets.put(lineId, lineTargets);
					} else {
						lineTargets = targets.get(lineId);
					}

					// Create or get set of targets for this source.
					HashSet<String> sourceTargets;
					if (lineTargets.containsKey(source) == false) {
						sourceTargets = new HashSet<String>();
						lineTargets.put(source, sourceTargets);
					} else {
						sourceTargets = lineTargets.get(source);
					}

					// Add source-target edge
					sourceTargets.add(target);
				}

				child = child.getNextSibling();
			}
		}

		// Convert edge sets to MetroLines by finding Euler trails
		ArrayList<MetroLine> metroLines = new ArrayList<MetroLine>();
		for (Entry<String, HashMap<String, HashSet<String>>> entry : targets.entrySet()) {
			HashMap<String, HashSet<String>> metroLineEdges = entry.getValue();
			MetroLine metroLine = createMetroLine(metroStations, metroLineEdges);
			metroLines.add(metroLine);
		}

		// Create MetroMap from collections of stations and lines.
		metroMap.initialize(metroStations.values(), metroLines);
	}

}
